package com.flashflexpro.flexdirector.conn{
    import flash.display.DisplayObject;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.ProgressEvent;
    import flash.net.Socket;
    import flash.utils.ByteArray;
    import flash.utils.Dictionary;


    public class ConnWithSocket extends EventDispatcher implements IDirectorConn{

        public function ConnWithSocket( socket:Socket, ui:DisplayObject = null ){
            _socket = socket;
            _socket.addEventListener( ProgressEvent.SOCKET_DATA, oonSocketData );
            /**
             * If pop up a html loading a swf in this air app, which connected back to this air app self's _socket, close event will be never fired!
             */
            _socket.addEventListener( Event.CLOSE, onSocketClose );
            if( ui != null ){
                _ui = ui;
                ui.addEventListener( Event.ENTER_FRAME, onEnterFrame );
                _callCache = [];
            }
        }


        protected function onSocketClose( event:Event ):void{
            try{
                if( connectionId != null ){
                    dispatchEvent( new RemoteCallingEvent( RemoteCallingEvent.DISCONNECTED ) );
                }
            }
            catch( e:Error ){
                CONFIG::debugging{
                    trace( this + ".onSocketClose: e:" + e.getStackTrace() );
                }
            }
            _socket.removeEventListener( ProgressEvent.SOCKET_DATA, oonSocketData );
            _socket.removeEventListener( Event.CLOSE, onSocketClose );
            _socket = null;
            if( _ui != null ){
                _ui.removeEventListener( Event.ENTER_FRAME, onEnterFrame )
            }
        }


        private var _buffer:ByteArray = new ByteArray();

        private var _lastExeCallIndex:uint = 0;

        private function oonSocketData( event:ProgressEvent ):void{
            //trace( "oonSocketData-->_lastExeCallIndex->" + _lastExeCallIndex);
            var tmpBA:ByteArray = new ByteArray();
            _socket.readBytes( tmpBA, 0 );
            var arr:Array;
            try{
                arr = bytes2Array( tmpBA );
            }
            catch( e:Error ){
                if( e.message == CROSS_DOMAIN || e.message == BUFFERING ){
                    return;
                }
                var s:String = new String( tmpBA );
                trace( s );
                throw e;
            }
            if( arr == null ){
                throw new Error( this + ".oonSocketData" );
            }
            onCalledWithArr( arr )
        }

        public static const CROSS_DOMAIN:String = "CROSS_DOMAIN";

        public static const BUFFERING:String = "BUFFERING";

        protected function bytes2Array( ba:ByteArray ):Array{
            trace( this + ".bytes2Array, _lastExeCallIndex:" + _lastExeCallIndex );
            var arr:Array = null;
            ba.position = 0;
            ba.readBytes( _buffer, _buffer.length );
            try{
                _buffer.uncompress();
            }
            catch( e:Error ){
                throw new Error( BUFFERING );
            }
            arr = _buffer.readObject();
            _buffer = new ByteArray();
            //trace( "ConnWithSockt<<<bytes2Array-->_lastExeCallIndex->" + _lastExeCallIndex);
            return arr;
        }

        private function onCalledWithArr( orgCallArr:Array ):void{
            while( orgCallArr.length > 0 ){
                tryFlushOrderCachedCall( orgCallArr.shift() );
            }
        }

        private function tryFlushOrderCachedCall( callArr:Array ):void{
            var receivedIndex:int = callArr.shift();
            CONFIG::debugging{
                trace( this + ".tryFlushOrderCachedCall.receivedIndex->" + receivedIndex + ";lastIndex->" +
                        _lastExeCallIndex );
            }
            if( receivedIndex == _lastExeCallIndex + 1 ){
                var name:String = callArr.shift();
                onPeerCalled( name, callArr );
                _lastExeCallIndex ++;

                var nextNum:int = _lastExeCallIndex + 1;
                var nextArr:Array = _receivedOrderCache[ nextNum ];
                if( nextArr != null ){
                    delete _receivedOrderCache[ nextNum ];
                    tryFlushOrderCachedCall( nextArr );
                }
            }
            else{
                _receivedOrderCache[ receivedIndex ] = callArr;
            }
        }

        private var _receivedOrderCache:Dictionary = new Dictionary();

        protected var _socket:Socket;
        public function get socket():Socket{
            return _socket;
        }

        protected var _ui:DisplayObject;
        public function get ui():DisplayObject{
            return _ui;
        }

        protected var _connectionId:String;
        public function get connectionId():String{
            return _connectionId;
        }

        public function destroy():void{
            if( _socket != null && _socket.connected ){
                try{
                    _socket.close();
                }
                catch( e:Error ){
                    trace( e.getStackTrace() );
                }
                _socket.dispatchEvent( new Event( Event.CLOSE ) );
            }
        }

        private var _sendIndex:uint = 1;

        private var _callCache:Array;

        private function onEnterFrame( event:Event ):void{
            if( _callCache.length > 0 ){
                var rt:ByteArray = new ByteArray();
                rt.writeObject( _callCache );
                rt.compress();
                _socket.writeBytes( rt, 0, rt.length );
                _socket.flush();
                _callCache = [];
            }
        }

        public function callPeer( methodName:String, ... args ):void{
            _callCache.push( [ _sendIndex, methodName ].concat( args ) );
            _sendIndex ++;
        }


        public function onPeerCalled( methodName:String, args:Array ):void{
            CONFIG::debugging{
                trace( this + ".onPeerCalled methodName:" + methodName );
            }
            dispatchEvent( new RemoteCallingEvent( methodName, args ) );
        }

        public function get connected():Boolean{
            return _socket.connected;
        }
    }
}